/*
 * display3000.c
 *
 * Created: 12.04.2013 16:49:40
 *  Author: Stefan
 */ 


#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <string.h>
#include "display3000.h"


#define LCD_CLK		1	// Clock
#define LCD_RESET	4	// Reset Display
#define LCD_SELECT	5	// Cable Select
#define LCD_DC		6	// Data/Command
#define LCD_DATA	2	// Send Data

#define SET(bit) (PORTB |= (1<<bit))
#define RESET(bit) (PORTB &= ~(1<<bit))


uint8_t WindowWidth = 0;
uint8_t WindowHeight = 0;

extern const uint8_t Font1[], Font2[];


void delay_ms(uint16_t period) {
	for(uint16_t i = 0; i < period; i++) {
		_delay_ms(1);
	}
}


inline void LCD_Wait(void) {
	//waits for SPI transfer is done
	while (SPCR & (1<<SPE)) {
		while (!(SPSR & (1<<SPIF)));
		SPCR &= ~(1<<SPE);
	}
}


inline void LCD_signal() {
	SET(LCD_SELECT);													// CSelect := 1
	__asm("nop");
	__asm("nop");
	RESET(LCD_SELECT);													// CSelect := 0	
}


inline void LCD_SPI_Byte(const unsigned char data[], unsigned int count) {
	//....data
	for (unsigned int i=0; i<count; ++i) {
		SPCR |= (1<<SPE);
		SPDR = data[i];
		LCD_Wait();
	}
}


inline void LCD_SPI_Int(unsigned int Value) {
	SPCR |= (1<<SPE);
	SPDR = (Value >> 8) & 0xff;
	LCD_Wait();
	SPCR |= (1<<SPE);
	SPDR = Value & 0xff;
	LCD_Wait();
}


void LCD_SPI_DBCommand(const unsigned int data[], unsigned int count) {
	for (int i=0; i<count; ++i) {
		RESET(LCD_SELECT);					// CSelect := 0
		SET(LCD_DC);						// DataCmd := 1
		unsigned char msbyte = (data[i] >> 8) & 0xff;		// msb!
		unsigned char lsbyte = data[i] & 0xff;			// lsb!
		SPCR |= (1<<SPE); 					//SPI: Ready
		SPDR = msbyte;						//send first 8 bits
		LCD_Wait();							//Wait until all bits has been sent
		SPCR |= (1<<SPE);
		SPDR = lsbyte;						//send last 8 bits
		LCD_Wait();
		RESET(LCD_DC);						// DataCmd := 0
	}
}


void LcdInit() {
	// PortB als Output
	DDRB = 0xFF;
	
	// SPI initialisieren
	SPSR = (1<<SPI2X);
	SPCR = (1<<SPE) | (1<<MSTR);
	
	// nach Display-Powerup warten
	delay_ms(300);
	
	// Init-Prozedur
	RESET(LCD_RESET);	delay_ms(75);
	SET(LCD_SELECT);	delay_ms(75);
	RESET(LCD_CLK);		delay_ms(75);
	SET(LCD_DC);		delay_ms(75);
	SET(LCD_RESET);		delay_ms(75);
	
	unsigned int init_data[] =
	{
		0xFDFD, 0xFDFD,
		// pause
		0xEF00, 0xEE04, 0x1B04, 0xFEFE, 0xFEFE,
		0xEF90, 0x4A04, 0x7F3F, 0xEE04, 0x4306,
		// pause
		0xEF90, 0x0983, 0x0800, 0x0BAF, 0x0A00,
		0x0500, 0x0600, 0x0700, 0xEF00, 0xEE0C,
		0xEF90, 0x0080, 0xEFB0, 0x4902, 0xEF00,
		0x7F01, 0xE181, 0xE202, 0xE276, 0xE183,
		0x8001, 0xEF90, 0x0000
	};

	LCD_SPI_DBCommand(&init_data[0], 2);	delay_ms(75);
	LCD_SPI_DBCommand(&init_data[2], 10);	delay_ms(75);
	LCD_SPI_DBCommand(&init_data[12], 23);
	RESET(LCD_SELECT);
	
}


// Ausgabebereich festlegen
void LcdWindow(uint8_t left, uint8_t top, uint8_t right, uint8_t bottom) {
	unsigned char data[] = {0xEF, 0x08, 0x18, 0x00, 0x12, left, 0x15, right, 0x13, top, 0x16, bottom};

	SET(LCD_DC);														// DataCmd := 1
	LCD_SPI_Byte(data, 12);
	SET(LCD_SELECT);
	RESET(LCD_DC);														// DataCmd := 0
	RESET(LCD_SELECT);
	
	WindowWidth = right - left + 1;
	WindowHeight = bottom - top + 1;
}


void LcdClear(uint16_t color) {
	LcdFillRect(0, 0, 131, 175, color);
}


void LcdFillRect(uint8_t left, uint8_t top, uint8_t right, uint8_t bottom, uint16_t color) {
	LcdWindow(left, top, right, bottom);
	
	for(unsigned int i = 0; i < WindowWidth * WindowHeight; i++) {
		LCD_SPI_Int(color);
	}

	//SET(LCD_SELECT);													// CSelect := 1
	//RESET(LCD_SELECT);													// CSelect := 0
	LCD_signal();
}


void LcdDrawRect(uint8_t left, uint8_t top, uint8_t right, uint8_t bottom, uint16_t color) {
	if ((top == bottom) || (left == right)) {
		LcdFillRect(left, top, right, bottom, color);
	} else {
		LcdFillRect(left, top, right, top, color);
		LcdFillRect(left, bottom, right, bottom, color);
		LcdFillRect(left, top + 1, left, bottom - 1, color);
		LcdFillRect(right, top + 1, right, bottom - 1, color);
	}	
}


void LcdPrint(const char* Text, const uint8_t Font[], uint8_t left, uint8_t top, uint8_t right, unsigned int forecolor, unsigned int backcolor) {
	const unsigned int Len = strlen(Text);
	const uint8_t FontMinWidth = pgm_read_byte(&Font[0]);
	const uint8_t FontMaxWidth = pgm_read_byte(&Font[1]);
	const uint8_t FontHeight = pgm_read_byte(&Font[2]);
	const uint8_t CharCount = pgm_read_byte(&Font[3]);
	uint8_t Char[CharCount];
	uint8_t CharIndex;
	uint8_t CharIndexUnknown = 0;
	uint8_t charleft = left;
	uint8_t width;
	uint8_t char_data_size;
	
	if (FontMaxWidth > 8) {
		char_data_size = 2 + FontHeight * 2;
	} else {
		char_data_size = 2 + FontHeight;
	}		
	
	for(uint8_t i = 0; i < CharCount; i++) {
		Char[i] = pgm_read_byte(&Font[4 + i * char_data_size]);
		
		if (Char[i] == '?') {
			CharIndexUnknown = i;
		}
	}
	
	for(uint8_t Pos = 0; Pos < Len; Pos++) {
		if (Text[Pos] == ' ') {
			charleft += FontMinWidth + 2;
			
			uint8_t left2 = charleft;
			
			for(uint8_t Pos2 = Pos + 1; Pos2 < Len; Pos2++) {
				if (Text[Pos2] == ' ') {
					break;
				} else {
					CharIndex = CharIndexUnknown;
					
					for(uint8_t i = 0; i < CharCount; i++) {
						if (Char[i] == Text[Pos2]) {
							CharIndex = i;
							break;
						}
					}
					
					width = pgm_read_byte(&Font[5 + CharIndex * char_data_size]);
					
					left2 += width;
					
					if (left2 > right) {
						break;
					}
				}
			}
		
			if (left2 > right) {
				charleft = left;
				top += FontHeight;
			}
		} else {
			if (Text[Pos] == '\n') {
				charleft = left;
				top += FontHeight;
			} else {				
				CharIndex = CharIndexUnknown;
		
				for(uint8_t i = 0; i < CharCount; i++) {
					if (Char[i] == Text[Pos]) {
						CharIndex = i;
						break;
					}
				}
		
				width = pgm_read_byte(&Font[5 + CharIndex * char_data_size]);
		
				if (charleft + width > right) {
					top += FontHeight;
					charleft = left;
				}
				
				LcdWindow(charleft, top, charleft + width - 1, top + FontHeight - 1);
		
				for(uint8_t y = 0; y < FontHeight; y++) {
					uint16_t Row;
			
					if (FontMaxWidth > 8) {
						Row = pgm_read_byte(&Font[6 + CharIndex * char_data_size + y * 2]) << 8;
						Row |= pgm_read_byte(&Font[7 + CharIndex * char_data_size + y * 2]);
					} else {
						Row = pgm_read_byte(&Font[6 + CharIndex * char_data_size + y]) << 8;
					}
			
					for (uint8_t x = 0; x < width; x++) {
						if (Row & (1<<15)) {
							LCD_SPI_Int(forecolor);
						} else {
							LCD_SPI_Int(backcolor);
						}
						Row <<= 1;
					}
				}
		
				charleft += width + 1;

				//SET(LCD_SELECT);													// CSelect := 1
				//RESET(LCD_SELECT);													// CSelect := 0
				LCD_signal();
			}			
		}		
	}
}